package cn.tedu.csmall.passport.service.impl;

import cn.tedu.csmall.commons.pojo.po.AdminLoginInfoPO;
import cn.tedu.csmall.commons.pojo.vo.PageData;
import cn.tedu.csmall.passport.cache.AdminJwtRepository;
import cn.tedu.csmall.passport.ex.ServiceException;
import cn.tedu.csmall.passport.mapper.AdminMapper;
import cn.tedu.csmall.passport.mapper.AdminRoleMapper;
import cn.tedu.csmall.passport.mapper.LoginLogMapper;
import cn.tedu.csmall.passport.pojo.dto.AdminLoginInfoDTO;
import cn.tedu.csmall.passport.pojo.entity.Admin;
import cn.tedu.csmall.passport.pojo.entity.AdminRole;
import cn.tedu.csmall.passport.pojo.entity.LoginLog;
import cn.tedu.csmall.passport.pojo.param.AdminAddNewParam;
import cn.tedu.csmall.passport.pojo.param.AdminLoginInfoParam;
import cn.tedu.csmall.passport.pojo.param.AdminUpdateInfoParam;
import cn.tedu.csmall.passport.pojo.vo.AdminListItemVO;
import cn.tedu.csmall.passport.pojo.vo.AdminStandardVO;
import cn.tedu.csmall.passport.security.AdminDetails;
import cn.tedu.csmall.passport.service.AdminService;
import cn.tedu.csmall.commons.util.PageInfoToPageDataConverter;
import cn.tedu.csmall.passport.web.ServiceCode;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Service
public class AdminServiceImpl implements AdminService {

    @Value("${csmall.jwt.secret-key}")
    private String secretKey;

    @Value("${csmall.jwt.duration-in-minute}")
    private Long duration;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AdminMapper adminMapper;
    @Autowired
    private AdminRoleMapper adminRoleMapper;
    @Autowired
    private LoginLogMapper loginLogMapper;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    AdminJwtRepository adminJwtRepository;

    @Override
    public AdminLoginInfoDTO login(AdminLoginInfoParam adminLoginInfoParam, String remoteAddr, String userAgent) {
        log.debug("开始处理【管理员登录】的请求，参数：{}", adminLoginInfoParam);

        // 创建认证时所需参数对象
        Authentication authentication = new UsernamePasswordAuthenticationToken(
                adminLoginInfoParam.getUsername(),
                adminLoginInfoParam.getPassword());

        // 执行认证，并获取认证结果
        Authentication authenticateResult = authenticationManager.authenticate(authentication);// 自动调用 UserDetailsServiceImpl 中的方法
        log.debug("验证登录完成！");

        // 将认证结果存入到SecurityContext中
        // SecurityContext securityContext = SecurityContextHolder.getContext();
        // securityContext.setAuthentication(authenticateResult);

        // 从认证结果中取出所需的数据
        Object principal = authenticateResult.getPrincipal();
        AdminDetails adminDetails = (AdminDetails) principal;// 认证结果中的当事人就是 UserDetailsServiceImpl 中返回的数据
        Collection<GrantedAuthority> authorities = adminDetails.getAuthorities();

        // 不需要将认证结果存入Security上下文中了，改为登陆成功后生成对应的JWT，并返回
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", adminDetails.getId());
        claims.put("username", adminDetails.getUsername());
        String authoritiesJsonString = JSON.toJSONString(authorities);
        // 不将权限放入jwt了，改为放入Redis作为value
        // claims.put("authoritiesJsonString", JSON.toJSONString(authorities));

        long tokenDuration = duration * 60 * 1000;
        Date tokenExpiration = new Date(System.currentTimeMillis() + tokenDuration);
        String jwt = Jwts.builder()
                // Header
                .setHeaderParam("alg", "HS256")
                .setHeaderParam("typ", "JWT")
                // Payload
                .setClaims(claims)
                // 过期时间（一天24小时）
                .setExpiration(tokenExpiration)
                // Verify Signature
                .signWith(SignatureAlgorithm.HS256, secretKey)
                // 生成
                .compact();
        log.debug("生成了此管理员的信息对应的JWT：{}", jwt);

        AdminLoginInfoPO adminLoginInfoPO = new AdminLoginInfoPO();
        adminLoginInfoPO.setId(adminDetails.getId());
        adminLoginInfoPO.setEnable(adminDetails.isEnabled() ? 1 : 0);
        adminLoginInfoPO.setRemoteAddr(remoteAddr);
        adminLoginInfoPO.setUserAgent(userAgent);
        adminLoginInfoPO.setAuthorityListJsonString(authoritiesJsonString);

        adminJwtRepository.saveLoginInfo(jwt, adminLoginInfoPO, tokenDuration);
        log.debug("向Redis中写入此管理员的登录信息：{}", adminLoginInfoPO);

        // 准备返回
        return new AdminLoginInfoDTO()
                .setId(adminDetails.getId())
                .setUsername(adminDetails.getUsername())
                .setToken(jwt)
                .setTokenExpiration(tokenExpiration);
    }

    @Override
    public void logout(String jwt) {
        log.debug("开始处理【管理员退出登录】的请求，参数：{}", jwt);
        adminJwtRepository.delete(jwt);
    }

    @Override
    public void addNew(AdminAddNewParam adminAddNewParam) {

        // 检查管理员用户名是否被占用
        QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", adminAddNewParam.getUsername());
        int countByUsername = adminMapper.selectCount(queryWrapper);
        log.debug("根据用户名统计匹配的管理员数量，结果为{}", countByUsername);
        if (countByUsername > 0) {
            String msg = "添加管理员失败，用户名被占用！";
            log.warn(msg);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, msg);
        }
        // 检查管理员手机号是否被占用，如果被占用，则抛出异常
        QueryWrapper<Admin> queryWrapper1 = new QueryWrapper<>();
        queryWrapper1.eq("phone", adminAddNewParam.getPhone());
        int countByPhone = adminMapper.selectCount(queryWrapper1);
        log.debug("根据手机号统计匹配的管理员数量，结果为{}", countByPhone);
        if (countByPhone > 0) {
            String msg = "添加管理员失败，手机号被占用！";
            log.warn(msg);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, msg);
        }

        // 检查管理员电子邮箱是否被占用
        QueryWrapper<Admin> queryWrapper2 = new QueryWrapper<>();
        queryWrapper2.eq("email", adminAddNewParam.getEmail());
        int countByEmail = adminMapper.selectCount(queryWrapper2);
        log.debug("根据邮箱统计匹配的管理员数量，结果为{}", countByEmail);
        if (countByEmail > 0) {
            String msg = "添加管理员失败，邮箱被占用！";
            log.warn(msg);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, msg);
        }

        // 执行插入将管理员数据插入数据库
        Admin admin = new Admin();
        BeanUtils.copyProperties(adminAddNewParam,admin);
        String encode = passwordEncoder.encode(adminAddNewParam.getPassword());
        admin.setPassword(encode);
        admin.setLastLoginIp(null);
        admin.setLoginCount(0);
        admin.setGmtLastLogin(null);
        admin.setGmtCreate(LocalDateTime.now());
        admin.setGmtModified(LocalDateTime.now());
        int rows = adminMapper.insert(admin);
        if (rows != 1) {
            String msg = "添加管理员失败，服务器忙，请稍后再试！";
            log.warn(msg);
            // 抛出异常就能回滚
            throw new ServiceException(ServiceCode.ERR_INSERT, msg);
        }
        log.debug("新的管理员数据插入到数据库完成！");

        // 将管理员与角色的关联数据插入数据库
        Long[] roleIds = adminAddNewParam.getRoleIds();
        AdminRole[] adminRoles = new AdminRole[roleIds.length];
        for (int i = 0; i < roleIds.length; i++) {
            AdminRole adminRole  = new AdminRole();
            adminRole.setAdminId(admin.getId());
            adminRole.setRoleId(roleIds[i]);
            adminRole.setGmtCreate(LocalDateTime.now());
            adminRole.setGmtModified(LocalDateTime.now());
            adminRoles[i] = adminRole;
        }
        rows = adminRoleMapper.insertBatch(adminRoles);
        if (rows != roleIds.length) {
            String msg = "添加管理员失败，服务器忙，请稍后再试！";
            log.warn(msg);
            throw new ServiceException(ServiceCode.ERR_INSERT, msg);
        }
        log.debug("将管理员与角色的关联数据插入到数据库完成！");
    }

    @Override
    public PageData<AdminListItemVO> list(Integer pageNum, Integer pageSize) {
        log.debug("开始处理【分页查询管理员列表】的业务，页码：{}，每页记录数：{}", pageNum, pageSize);
        PageHelper.startPage(pageNum, pageSize);
        List<AdminListItemVO> list = adminMapper.list();
        PageInfo<AdminListItemVO> pageInfo = new PageInfo<>(list);
        return PageInfoToPageDataConverter.convert(pageInfo);
    }

    @Override
    public PageData<AdminListItemVO> list(Integer pageNum) {
        Integer pageSize = 5;
        return list(pageNum,pageSize);
    }

    @Override
    public void deleteById(Long adminId) {
        // 根据id查询数据库中有没有该管理员
        QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id", adminId);
        int countById = adminMapper.selectCount(queryWrapper);
        if (countById == 0) {
            String message = "删除失败，没有该管理员ID！";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        // 检查是否有角色关联到了此管理员，如果有，则删除管理员所对应的角色
        QueryWrapper<AdminRole> adminRoleQueryWrapper = new QueryWrapper<>();
        adminRoleQueryWrapper.eq("admin_id",adminId);
        List<AdminRole> adminRoles = adminRoleMapper.selectList(adminRoleQueryWrapper);
        // 如果能根据 adminId 查到数据，则执行删除
        if (adminRoles.size() > 0) {
            Collection<Long> ids = new ArrayList<>();
            for (AdminRole adminRole : adminRoles) {
                ids.add(adminRole.getId());
            }
            adminRoleMapper.deleteBatchIds(ids);
        }

        // 检查该管理员是否有管理员登录日志，如果有，则删除管理员所对应的登录日志
        QueryWrapper<LoginLog> loginLogQueryWrapper = new QueryWrapper<>();
        loginLogQueryWrapper.eq("admin_id",adminId);
        LoginLog loginLog = loginLogMapper.selectOne(loginLogQueryWrapper);
        // 如果能根据 adminId 查到数据，则执行在 ams_login_log 表中根据id删除数据
        if (loginLog != null) {
            loginLogMapper.deleteById(loginLog.getId());
        }

        // 该管理员没有附属的东西，执行删除
        adminMapper.deleteById(adminId);
    }

    @Override
    public void updateInfoById(Long id, AdminUpdateInfoParam adminUpdateInfoParam) {
        // 修改前判断数据是否存在
        QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id",id);
        int countById = adminMapper.selectCount(queryWrapper);
        if (countById == 0) {
            String message = "【修改管理员详情失败，没有该管理员！】";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        // 修改前判断管理员名称是否被其他管理员占用，如果占用则抛出异常
        QueryWrapper<Admin> queryWrapper2 = new QueryWrapper<>();
        queryWrapper2.eq("username",adminUpdateInfoParam.getUsername())
                .ne("id",id);
        int countByName = adminMapper.selectCount(queryWrapper2);
        if (countByName > 0) {
            String message = "【修改管理员信息失败，管理员名称被占用！】";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }

        // 修改前判断管理员手机号是否被其他管理员占用，如果占用则抛出异常
        QueryWrapper<Admin> queryWrapper3 = new QueryWrapper<>();
        queryWrapper3.eq("phone",adminUpdateInfoParam.getPhone())
                .ne("id",id);
        int countByPhone = adminMapper.selectCount(queryWrapper3);
        if (countByPhone > 0) {
            String message = "【修改管理员信息失败，管理员手机号被占用！】";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }

        // 修改前判断管理员邮箱是否被其他管理员占用，如果占用则抛出异常
        QueryWrapper<Admin> queryWrapper4 = new QueryWrapper<>();
        queryWrapper4.eq("email",adminUpdateInfoParam.getEmail())
                .ne("id",id);
        int countByEmail = adminMapper.selectCount(queryWrapper4);
        if (countByEmail > 0) {
            String message = "【修改管理员信息失败，管理员电子邮箱被占用！】";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
        }

        // 执行修改
        log.debug("开始处理【修改管理员详情】的业务，参数：{}",adminUpdateInfoParam);
        Admin admin = new Admin();
        BeanUtils.copyProperties(adminUpdateInfoParam, admin);
        admin.setId(id);
        admin.setGmtModified(LocalDateTime.now());
        int rows = adminMapper.updateById(admin);
        if (rows != 1) {
            String msg = "修改管理员详情失败，服务器忙，请稍后再试！";
            log.warn(msg);
            // 抛出异常就能回滚
            throw new ServiceException(ServiceCode.ERR_UPDATE, msg);
        }
        log.debug("新的管理员数据更新到数据库完成！");
    }

    @Override
    public AdminStandardVO getStandardById(Long id) {
        log.debug("开始处理【根据ID查询管理员】的业务，ID值为：{}", id);
        AdminStandardVO result = adminMapper.getStandardById(id);
        if (result == null) {
            String message = "【根据ID查询管理员失败，管理员数据不存在！】";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }
        return result;
    }

    @Override
    public void updatePwdById(Long id, String pwd) {
        // 修改前判断数据是否存在
        QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id",id);
        int countById = adminMapper.selectCount(queryWrapper);
        if (countById == 0) {
            String message = "【修改管理员密码失败，没有该管理员！】";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        String encode = passwordEncoder.encode(pwd);
        Admin admin = new Admin()
                .setId(id)
                .setPassword(encode)
                .setGmtModified(LocalDateTime.now());
        int rows = adminMapper.updateById(admin);
        if (rows != 1) {
            String msg = "修改管理员密码失败，服务器忙，请稍后再试！";
            log.warn(msg);
            // 抛出异常就能回滚
            throw new ServiceException(ServiceCode.ERR_UPDATE, msg);
        }
        log.debug("新的管理员数据更新到数据库完成！");
    }

    @Override
    public void setEnable(Long id, Integer enable) {
        // 修改前判断数据是否存在
        QueryWrapper<Admin> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("id",id);
        int countById = adminMapper.selectCount(queryWrapper);
        if (countById == 0) {
            String message = "【设置启用或者禁用失败，没有该管理员！】";
            log.debug(message);
            throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
        }

        Admin admin = new Admin()
                .setId(id)
                .setEnable(enable)
                .setGmtModified(LocalDateTime.now());
        int rows = adminMapper.updateById(admin);
        if (rows != 1) {
            String msg = "设置启用或者禁用失败，服务器忙，请稍后再试！";
            log.warn(msg);
            // 抛出异常就能回滚
            throw new ServiceException(ServiceCode.ERR_UPDATE, msg);
        }
        log.debug("设置管理员的启用或者禁用到数据库完成！");
    }
}
